/** ***************************************************************************
 * CoffeeTexture.cpp
 * 2012 CoffeeParty Ltd.
 * Date Created: 2012-07-21                                  format YYYY-MM-DD
 * Revision Id: $Id$
 ******************************************************************************
 */

#include "CoffeeCommons.hpp"
#include "CoffeeTypes.hpp"

#include "CoffeeTexture.hpp"

CoffeeTexture::CoffeeTexture(SugarImage *_image, c_voidp mipmapData)
: image(0), mipimage(0), textureId(0), internalFormat(0), keepData(false)
{
    getImageValues(_image);
    glGenTextures(1, &textureId);

    image = new SugarRaw(_image);

    if (!image)
    {
        c_print("No image to process");
        return;
    }

    createTextureFromImage();

    if (!keepData)
        drop(image);
}

CoffeeTexture::CoffeeTexture(SugarImage *_image, c_bool invertRedBlue)
: image(0), mipimage(0), textureId(0), internalFormat(0), keepData(false)
{
    getImageValues(_image);
    glGenTextures(1, &textureId);

    image = new SugarRaw(_image);

    if (!image)
    {
        c_print("No image to process");
        return;
    }

    createTextureFromImage(invertRedBlue);

    if (!keepData)
        drop(image);
}

CoffeeTexture::~CoffeeTexture()
{
    if (textureId)
        glDeleteTextures(1, &textureId);

    if (image)
        drop(image);
}

c_void CoffeeTexture::createTextureFromImage(c_bool invertRedBlue, c_bool hasMipMap)
{
    glBindTexture(GL_TEXTURE_2D, textureId);

    GLint filtering = 0;
    GLenum pixelFormat = 0;
    GLenum pixelType = 0;

    internalFormat = getParameters(image->getBitsPerPixel(), filtering,
        pixelFormat, pixelType);

    if (hasMipMap)
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }
    else
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }

    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, image->getDimension().x,
        image->getDimension().y, 0, invertRedBlue ? pixelFormat : internalFormat,
        pixelType, image->ark());

    image->unark();
}

c_void CoffeeTexture::testDrawOGL(c_dimension<c_int> pixel, c_int pad)
{
    glBindTexture(GL_TEXTURE_2D, textureId);
    glBegin(GL_QUADS);
        glTexCoord2i(0, 1); glVertex3i(pixel.x + pad, pixel.y + pad, 0);
        glTexCoord2i(0, 0); glVertex3i(pixel.x + pad, pixel.y + imageSize.y + pad, 0);
        glTexCoord2i(1, 0); glVertex3i(pixel.x + imageSize.x + pad, pixel.y + imageSize.y + pad, 0);
        glTexCoord2i(1, 1); glVertex3i(pixel.x + imageSize.x + pad, pixel.y + pad, 0);
    glEnd();
}

c_voidp CoffeeTexture::ark(c_bool readonly, c_uint mipmapLevel)
{
    SugarImage *_image = (!mipmapLevel) ? image : mipimage;
    readonlylock |= readonly;
    miplevelstored = mipmapLevel;

    if (!_image)
    {
        if (mipmapLevel)
        {
            c_uint i = 0;
            c_uint width = textureSize.x;
            c_uint height = textureSize.y;

            do
            {
                if (width > 1)
                    width >>= 1;
                if (height > 1)
                    height >>= 1;
                ++i;
            }
            while (i != mipmapLevel);
            mipimage = _image;
        }
        else image = _image;
    }

    if (!_image)
        return 0;

    c_ubytep pixels = static_cast<c_ubytep>(_image->ark());
    if (!pixels)
        return 0;

    GLint tmpTexture;
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &tmpTexture);
    glBindTexture(GL_TEXTURE_2D, textureId);

    ///< Flip Image
    c_const_int pitch = _image->getPitch();
    c_ubytep p2 = pixels + (_image->getDimension().y - 1) * pitch;
    c_ubytep tmpBuffer = (c_ubytep)malloc(pitch);

    for (c_uint i = 0; i < _image->getDimension().y; i += 2)
    {
        memcpy(tmpBuffer, pixels, pitch);
        memcpy(pixels, p2, pitch);
        memcpy(p2, tmpBuffer, pitch);
        pixels += pitch;
        p2 -= pitch;
    }

    free(tmpBuffer);
    tmpBuffer = 0;

    _image->unark();
    glBindTexture(GL_TEXTURE_2D, tmpTexture);
    return _image->ark();
}

c_void CoffeeTexture::unark()
{
    image->unark();
    readonlylock = false;
}

c_dimension<c_uint> CoffeeTexture::getOptimalSize(c_dimension<c_uint>& size, c_bool rulePowerOfTwo,
    c_bool ruleSquare, c_bool larger, c_uint maxValue) const
{
    c_uint w = 1;
    c_uint h = 1;

    if (rulePowerOfTwo)
    {
        while ( w < (c_uint)size.x)
            w <<= 1;

        if (!larger && w != 1 && w != (c_uint)size.x)
            w >>= 1;

        while (h < (c_uint)size.y)
            h <<= 1;

        if (!larger && h != 1 && h != (c_uint)size.y)
            h >>= 1;
    }
    else
    {
        w = (c_uint)size.x;
        h = (c_uint)size.y;
    }

    if (ruleSquare)
    {
        if ((larger && (w > h)) || (!larger && (w < h)))
            h = w;
        else w = h;
    }

    if (maxValue > 0 && w > maxValue)
        w = maxValue;

    if (maxValue > 0 && h > maxValue)
        h = maxValue;

    return c_dimension<c_uint>(w, h);
}

c_void CoffeeTexture::getImageValues(SugarImage *_image)
{
    if (!_image)
    {
        c_print("No image for CoffeeTexture");
        return;
    }

    imageSize = _image->getDimension();

    if (!imageSize.x || !imageSize.y)
    {
        c_print("Invalid size of image");
        return;
    }

    textureSize = getOptimalSize(imageSize);
}

GLint CoffeeTexture::getParameters(c_uint bits, GLint& filtering, GLenum& colorformat, GLenum& type)
{
    filtering = GL_LINEAR;
    colorformat = GL_RGBA;
    type = GL_UNSIGNED_BYTE;

    switch(bits)
    {
    case 16:
        colorformat = GL_BGRA_EXT;
        type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
        return GL_RGBA;
    case 17:
        colorformat = GL_BGR;
        type = GL_UNSIGNED_SHORT_5_6_5_REV;
        return GL_RGB;
    case 24:
        colorformat = GL_BGR;
        type = GL_UNSIGNED_BYTE;
        return GL_RGB;
    case 32:
        colorformat = GL_BGRA_EXT;
        type = GL_UNSIGNED_BYTE;
        type = GL_UNSIGNED_INT_8_8_8_8_REV;
        return GL_RGBA;
    }

    return GL_RGBA8;
}

c_void CoffeeTexture::saveTexture(c_bool newTexture, c_voidp _mimapData, c_uint level)
{
    SugarImage *_image = level ? mipimage : image;
    if (!image)
    {
        c_print("No image to save");
        return;
    }

    GLenum oldFormat = internalFormat;
    GLint filtering = 0;
    GLenum pixelFormat = 0;
    GLenum pixelType = 0;

    internalFormat = getParameters(_image->getBitsPerPixel(), filtering,
        pixelFormat, pixelType);

    if (!newTexture)
        internalFormat = oldFormat;

    if (mipmap)
    {
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }
    else
    {
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }

    c_voidp source = _image->ark();
    if (newTexture)
        glTexImage2D(GL_TEXTURE_2D, level, internalFormat, _image->getDimension().x,
            _image->getDimension().y, 0, pixelFormat, pixelType, source);
    else glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, _image->getDimension().x,
            _image->getDimension().y, pixelFormat, pixelType, source);

    _image->unark();
}

const c_dimension<c_uint>& CoffeeTexture::getOriginalSize() const
{
    return imageSize;
}

const c_dimension<c_uint>& CoffeeTexture::getSize() const
{
    return textureSize;
}

GLuint CoffeeTexture::getTextureId() const
{
    return textureId;
}

c_uint CoffeeTexture::getPitch() const
{
    if (image)
        return image->getPitch();
    return 0;
}

c_bool CoffeeTexture::hasMipMaps() const
{
    return mipmap;
}

